package com.sevenwonders.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.sevenwonders.core.Resource.Type;
import com.sevenwonders.util.Helper;

// TODO:  This should eventually be abstract
public class Player {
    enum VictoryPointCategory {
        MILITARY,
        MONEY,
        WONDER,
        CIVILIAN,
        COMMERCIAL,
        GUILD,
        SCIENTIFIC
    }

    private String name;
    private City city;
    private Map<CardType, List<Card>> cardsInPlay = new HashMap<CardType, List<Card>>();
    private List<Card> hand;

    private List<Resource> tradeableResources = new ArrayList<Resource>();
    private List<Resource> tradeableComboResources = new ArrayList<Resource>();
    private List<Resource> comboResources = new ArrayList<Resource>();

    private int military = 0;
    private List<Integer> militaryTokens = new ArrayList<Integer>();
    private int money = 3;
    private Map<Science, Integer> science = new HashMap<Science, Integer>();
    private Map<VictoryPointCategory, Integer> victoryPoints = new HashMap<VictoryPointCategory, Integer>();

    protected Player(String name) {
        setName(name);

        science.put(Science.ENGINEERING, 0);
        science.put(Science.MATHEMATICS, 0);
        science.put(Science.WRITING, 0);
        science.put(Science.CHOOSE, 0);

        victoryPoints.put(VictoryPointCategory.MILITARY, 0);
        victoryPoints.put(VictoryPointCategory.MONEY, 1);
        victoryPoints.put(VictoryPointCategory.WONDER, 0);
        victoryPoints.put(VictoryPointCategory.CIVILIAN, 0);
        victoryPoints.put(VictoryPointCategory.COMMERCIAL, 0);
        victoryPoints.put(VictoryPointCategory.GUILD, 0);
        victoryPoints.put(VictoryPointCategory.SCIENTIFIC, 0);
    }

    public String getName() {
        return name;
    }

    void setName(String name) {
        this.name = name;
    }

    public City getCity() {
        return city;
    }

    void setCity(City city) {
        this.city = city;
    }

    public Map<CardType, List<Card>> getCardsInPlay() {
        return cardsInPlay;
    }

    public int getCardCount(CardType type) {
        if (type == null) {
            return 0;
        }

        List<Card> cardList = cardsInPlay.get(type);

        if (cardList == null) {
            return 0;
        }

        return cardList.size();
    }

    public boolean hasCardInPlay(Card card) {
        if (card != null) {
            for (List<Card> cardList : cardsInPlay.values()) {
                if (cardList.contains(card)) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean hasCardInPlay(BaseCard bc) {
        if (bc != null) {
            for (List<Card> cardList : cardsInPlay.values()) {
                for (Card card : cardList) {
                    if (bc == card.getBaseCard()) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    public boolean hasCardInPlay(String name) {
        if (name != null) {
            for (List<Card> cardList : cardsInPlay.values()) {
                for (Card card : cardList) {
                    if (name.equals(card.getName())) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    boolean canBuild(Card card) {
        if (hasCardInPlay(card.getCostCard())) {
            return true;
        }

        boolean rtn = true;
        int moneyLeft = money - card.getCostCoin();

        if (moneyLeft < 0) {
            rtn = false;
        }

        if (!Helper.isEmpty(card.getCostResource())) {
            List<Resource> myResources = getTradeableResources();
            List<Resource> cost = Arrays.asList(card.getCostResource());
            int toRemove = -1;

            for (Iterator<Resource> it = cost.iterator(); it.hasNext();) {
                Resource resource = it.next();
                toRemove = resource.indexInList(myResources);

                if (toRemove > -1) {
                    it.remove();
                    myResources.remove(toRemove);
                }
            }

            if (!Helper.isEmpty(cost)) {
                myResources = getAllComboResources();

                for (Iterator<Resource> it = cost.iterator(); it.hasNext();) {
                    Resource resource = it.next();
                    toRemove = resource.indexInList(myResources);

                    if (toRemove > -1) {
                        it.remove();
                        myResources.remove(toRemove);
                    }
                }

                if (!Helper.isEmpty(cost)) {
                    if (moneyLeft == 0) {
                        return false;
                    }

                    // See if player can buy resources
                    List<Player> neighbors = Game.myGame.getNeighbors(this);
                    boolean hasFirstTradingPost = hasCardInPlay(BaseCard.WEST_TRADING_POST);
                    boolean hasSecondTradingPost = false;
                    boolean hasMarketplace = hasCardInPlay(BaseCard.MARKETPLACE);

                    if (hasCardInPlay(BaseCard.EAST_TRADING_POST)) {
                        if (hasFirstTradingPost) {
                            hasSecondTradingPost = true;
                        } else {
                            Player temp = neighbors.get(0);
                            neighbors.set(0, neighbors.get(1));
                            neighbors.set(1, temp);
                            hasFirstTradingPost = true;
                        }
                    }

                    for (Iterator<Resource> it = cost.iterator(); it.hasNext();) {
                        Resource resource = it.next();

                        for (int i = 0; i < 2; i++) {
                            Player neighbor = neighbors.get(i);
                            List<Resource> resources = neighbor.getAllTradeableResources();
                            toRemove = resource.indexInList(resources);

                            if (toRemove > -1) {
                                it.remove();
                                resources.remove(toRemove);
                                int amount = (resource.getType() == Type.RAW ? (i == 0 ? (hasFirstTradingPost ? 1 : 2) : (hasSecondTradingPost ? 1 : 2))
                                    : (hasMarketplace ? 1 : 2));

                                if (moneyLeft < amount) {
                                    return false;
                                } else {
                                    moneyLeft -= amount;
                                }
                            }
                        }
                    }
                }
            }
        }

        return rtn;
    }

    boolean addCard(Card card) {
        List<Player> neighbors = Game.myGame.getNeighbors(this);

        if (card == null) {
            return false;
        } else if (!canBuild(card)) {
            return false;
        } else if (CardType.RAW_MATERIALS == card.getType()) {
            if (hasCardInPlay(BaseCard.HAVEN)) {
                addVictoryPoints(VictoryPointCategory.COMMERCIAL, 1);
            } else if (hasCardInPlay(BaseCard.SHIPOWNERS_GUILD)) {
                addVictoryPoints(VictoryPointCategory.GUILD, 1);
            }

            for (Player neighbor : neighbors) {
                if (neighbor.hasCardInPlay(BaseCard.WORKERS_GUILD)) {
                    neighbor.addVictoryPoints(VictoryPointCategory.GUILD, 1);
                }
            }
        } else if (CardType.MANUFACTURED_GOODS == card.getType()) {
            if (hasCardInPlay(BaseCard.SHIPOWNERS_GUILD)) {
                addVictoryPoints(VictoryPointCategory.GUILD, 1);
            } else if (hasCardInPlay(BaseCard.CHAMBER_OF_COMMERCE)) {
                addVictoryPoints(VictoryPointCategory.COMMERCIAL, 2);
            }

            for (Player neighbor : neighbors) {
                if (neighbor.hasCardInPlay(BaseCard.CRAFTMENS_GUILD)) {
                    neighbor.addVictoryPoints(VictoryPointCategory.GUILD, 2);
                }
            }
        } else if (CardType.COMMERCIAL == card.getType()) {
            if (hasCardInPlay(BaseCard.LIGHTHOUSE)) {
                addVictoryPoints(VictoryPointCategory.COMMERCIAL, 1);
            }

            for (Player neighbor : neighbors) {
                if (neighbor.hasCardInPlay(BaseCard.TRADERS_GUILD)) {
                    neighbor.addVictoryPoints(VictoryPointCategory.GUILD, 1);
                }
            }
        } else if (CardType.SCIENTIFIC == card.getType()) {
            for (Player neighbor : neighbors) {
                if (neighbor.hasCardInPlay(BaseCard.PHILOSOPHERS_GUILD)) {
                    neighbor.addVictoryPoints(VictoryPointCategory.GUILD, 1);
                }
            }
        } else if (CardType.MILITARY == card.getType()) {
            for (Player neighbor : neighbors) {
                if (neighbor.hasCardInPlay(BaseCard.SPIES_GUILD)) {
                    neighbor.addVictoryPoints(VictoryPointCategory.GUILD, 1);
                }
            }
        } else if (CardType.CIVILIAN == card.getType()) {
            for (Player neighbor : neighbors) {
                if (neighbor.hasCardInPlay(BaseCard.MAGISTRATES_GUILD)) {
                    neighbor.addVictoryPoints(VictoryPointCategory.GUILD, 1);
                }
            }
        } else if (CardType.GUILD == card.getType() && hasCardInPlay(BaseCard.SHIPOWNERS_GUILD)) {
            addVictoryPoints(VictoryPointCategory.GUILD, 1);
        }

        List<Card> cardList = cardsInPlay.get(card.getType());

        if (cardList == null) {
            cardList = new ArrayList<Card>();
            cardsInPlay.put(card.getType(), cardList);
        }

        boolean rtn = cardList.add(card);
        card.awardBonus(this);
        return rtn;
    }

    public List<Card> getHand() {
        return hand;
    }

    void setHand(List<Card> hand) {
        this.hand = hand;
    }

    public List<Resource> getTradeableResources() {
        return tradeableResources;
    }

    boolean addTradeableResource(Resource resource) {
        if (resource == null) {
            return false;
        }

        return tradeableResources.add(resource);
    }

    public List<Resource> getTradeableComboResources() {
        return tradeableComboResources;
    }

    boolean addTradeableComboResource(Resource resource) {
        if (resource == null) {
            return false;
        }

        return tradeableComboResources.add(resource);
    }

    public List<Resource> getAllTradeableResources() {
        List<Resource> all = new ArrayList<Resource>();
        all.addAll(tradeableResources);
        all.addAll(tradeableComboResources);
        return all;
    }

    public List<Resource> getComboResources() {
        return comboResources;
    }

    boolean addComboResource(Resource resource) {
        if (resource == null) {
            return false;
        }

        return comboResources.add(resource);
    }

    public List<Resource> getAllComboResources() {
        List<Resource> all = new ArrayList<Resource>();
        all.addAll(tradeableComboResources);
        all.addAll(comboResources);
        return all;
    }

    public List<Resource> getAllResources() {
        List<Resource> all = new ArrayList<Resource>();
        all.addAll(tradeableResources);
        all.addAll(tradeableComboResources);
        all.addAll(comboResources);
        return all;
    }

    public int getMilitary() {
        return military;
    }

    int addMilitary(int m) {
        military += m;
        return military;
    }

    public List<Integer> getMilitaryTokens() {
        return militaryTokens;
    }

    public int getMilitaryTokenCount(int mt) {
        int count = 0;

        for (int i : militaryTokens) {
            if (i == mt) {
                count++;
            }
        }

        return count;
    }

    boolean addMilitaryToken(int mt) {
        addVictoryPoints(VictoryPointCategory.MILITARY, mt);
        return militaryTokens.add(mt);
    }

    public int getMoney() {
        return money;
    }

    int addMoney(int m) {
        money += m;
        victoryPoints.put(VictoryPointCategory.MONEY, money / 3);
        return money;
    }

    public Map<Science, Integer> getScience() {
        return science;
    }

    int addScience(Science s) {
        if (s == null) {
            return 0;
        }

        int count = science.get(s);
        int newCount = count++;
        science.put(s, newCount);
        addVictoryPoints(VictoryPointCategory.SCIENTIFIC, calculateScienceScore() - victoryPoints.get(VictoryPointCategory.SCIENTIFIC));
        return newCount;
    }

    int calculateScienceScore() {
        int score = 0;
        int minCount = 99;
        int choose = 0;

        for (Entry<Science, Integer> entry : science.entrySet()) {
            if (entry.getKey() == Science.CHOOSE) {
                choose = entry.getValue();
            } else {
                int count = entry.getValue();
                score += (count * count);

                if (count < minCount) {
                    minCount = count;
                }
            }
        }

        if (choose > 0) {
            // TODO:  Handling the choose one science bonus (Scientists Guild, Babylon)
        }

        score += (minCount * 7);
        return score;
    }

    public Map<VictoryPointCategory, Integer> getVictoryPoints() {
        return victoryPoints;
    }

    public int getScore(VictoryPointCategory category) {
        if (category == null) {
            return 0;
        }

        return victoryPoints.get(category);
    }

    public int getScore() {
        int score = 0;

        for (Entry<VictoryPointCategory, Integer> entry : victoryPoints.entrySet()) {
            score += entry.getValue();
        }

        return score;
    }

    int addVictoryPoints(VictoryPointCategory category, int vp) {
        if (category == null) {
            return 0;
        }

        int categoryPoints = victoryPoints.get(category) + vp;
        victoryPoints.put(category, categoryPoints);

        return categoryPoints;
    }
}
